package speechrecorder;
 	
 	
 	import java.awt.Container;
 	import java.awt.event.*;
 	import java.io.*;
 	import java.net.*;
 	import javax.sound.sampled.*;
 	import javax.swing.*;
 	
 	
 	/**
 	 * @author  kmaclean
 	 */
 	public class PlayerApplet extends JApplet {
 	
 	    private Downloader dl;
 	    private Playback pl;
 	 // !!!!!!   
 	 //   private static final boolean DEBUG = false; // Whether to output messages to the console
 	    private static final boolean DEBUG = true; // Whether to output messages to the console
 	    
 	// !!!!!!   
 	    private URL url; // The URL as supplied via applet parameter
 	    private File tmpFile; // The remote data is downloaded and stored here
 	    private long fileSize; // The filesize, irrespective of how much is downloaded
 	    private long bytesDownloaded = 0L;
 	   
 	    private String cookie;
 	   
 	    private InputStream urlInputStream;
 	    private OutputStream fileOutStream;
 	    private InputStream fileInputStream;
 	    private AudioInputStream audioInputStream;
 	    private AudioFormat audioFormat;
 	    private AudioFormat targetFormat;
 	    private SourceDataLine sourceDataLine;
 	   
 	    private JProgressBar progressBar;
 	    private JSlider slider;
 	    private JButton playBut;
 	
 	    private boolean headerBytesLoaded = false; // Ogg Speex takes 108 (28+80) bytes for header
 	    private boolean audioInitialised = false;
 	
 	    private boolean isPlaying = false;
 	
 	    public void init(){
 	        layoutGui();
 	                getParameters();
 	                try {
 	                        initialiseStreams();
 	                dl = new Downloader();
 	                dl.start();
 	                pl = new Playback();                   
 	                } catch (IOException err){
 	            reportError("Unable to get an input stream from specified URL", err);
 	            // System.exit(1);
 	        }
 	       
 	        //layoutGui();
 	                        
 	
 	    }
 	
 	    /**
 	    * May halt applet if required param (file) does not exist
 	    */
 	    private void getParameters() {
 	        try {
 	// !!!!!!               
 	            String fileParam = getParameter("file");
 	            System.err.println("Downloading: " + fileParam);     
 	
 	          url = new URL(fileParam);
 	//  url = new URL("http://www.dev.voxforge.org/svn/de/Trunk/Audio/Main/8kHz_16bit/AdrianTovar-20080727-bxq/wav/deasdf68.wav");
 	// !!!!!!           
 	        } catch (NullPointerException err){
 	            reportError("\"file\" parameter is required - not found", err);
 	            // System.exit(1);
 	        } catch (MalformedURLException err){
 	            reportError("\"file\" parameter is not a correctly-formed URL", err);
 	            // System.exit(1);
 	        }
 	        try {
 	            cookie = getParameter("cookie");
 	            System.err.println("RecorderApplet Cookie: " + cookie +":\n"); 
 	        } catch (NullPointerException err){
 	            reportError("\"cookie\" parameter not found - this may cause problems trying to read the audio file", err);
 	            cookie = null;
 	        }
 	    }
 	   
 	    /**
 	    * Set up download, cache writing, and cache reading streams.
 	    */
 	    private void initialiseStreams() throws IOException {
 	       
 	        // try {
 	            URLConnection conn = url.openConnection();
 	           
 	            // The cookie value should be a parameter passed in to the applet
 	            if(cookie != null) {
 	                conn.setRequestProperty("Cookie", cookie);
 	            }
 	           
 	            conn.connect();
 	            urlInputStream = new BufferedInputStream(conn.getInputStream());
 	            fileSize = conn.getContentLength();
 	           
 	            if(DEBUG){
 	                if(fileSize == -1){
 	                    System.err.println("initialiseStreams(): File size cannot be determined for the remote file.");
 	                } else {
 	                    System.err.println("initialiseStreams(): File size for the remote file is " + fileSize);
 	                }
 	            }
 	           
 	       // }
 	        
 	        try {
 	            tmpFile = File.createTempFile("vf-", ".wav");
 	            tmpFile.deleteOnExit();
 	            System.err.println("MSPA tmpFile = " + tmpFile);
 	        } catch (IOException err){
 	            reportError("Unable to create temp file for storing data", err);
 	            // System.exit(1);
 	        }
 	       
 	        try {
 	            fileOutStream = new BufferedOutputStream(
 	                 new FileOutputStream(tmpFile));
 	        } catch (Exception err){
 	            reportError("Unable to open stream for writing audio data to temp file", err);
 	            //System.exit(1);
 	        }
 	       
 	        try {
 	            fileInputStream = new BufferedInputStream(new FileInputStream(tmpFile));
 	        } catch (IOException err){
 	            reportError("Unable to get an input stream to read the temp file's audio data", err);
 	            // System.exit(1);
 	        }
 	    }
 	   
 	    /**
 	    * Create GUI elements
 	    */
 	    private void layoutGui(){
 	
 	       Container pane = getContentPane();
 	       
 	       pane.setLayout(new BoxLayout(pane, BoxLayout.X_AXIS));
 	
 	       playBut = new JButton("Play");
 	       playBut.setEnabled(false);
 	       playBut.addActionListener(new ActionListener(){
 	                       public void actionPerformed(ActionEvent e){
 	                           if(playBut.getText().equals("Stop")){
 	                               pl.stopPlayback();
 	                           }else{
 	                               pl.startPlayback();
 	                           }
 	                       }
 	                       });
 	       
 	       progressBar = new JProgressBar();
 	       progressBar.setString("Loading");
 	       progressBar.setStringPainted(true);
 	       progressBar.setIndeterminate(fileSize == -1);
 	       
 	       pane.add(playBut);
 	       pane.add(progressBar);
 	    }
 	   
 	    /**
 	    * Called by the download thread when enough file has downloaded to be able
 	    * to interpret the audio format from the header
 	    */
 	    private void initialiseAudio(){
 	       
 	       
 	        try{
 	            fileInputStream = new BufferedInputStream(new FileInputStream(tmpFile));
 	           
 	       
 	            AudioFileFormat aff = AudioSystem.getAudioFileFormat(fileInputStream);
 	           
 	           
 	           
 	           
 	            System.err.println("AudioFileFormat derived from stream is: " + aff);
 	            System.err.println("AudioFileFormat.Type is: " + aff.getType());
 	            System.err.println("AudioFormat is: " + aff.getFormat());
 	           
 	            audioFormat = aff.getFormat();
 	            DataLine.Info info = new DataLine.Info(SourceDataLine.class, audioFormat);
 	
 	
 	            if(DEBUG){
 	                if (!AudioSystem.isLineSupported(info)) {
 	                    System.err.println("AudioSystem does not support this DataLine.Info (made out of the AudioFormat): " + info);
 	                } else {
 	                    System.err.println("AudioSystem supports this DataLine.Info (made out of the AudioFormat): " + info);
 	                }
 	            }
 	//          !!!!!!
 	/*             targetFormat = new AudioFormat(
 	                AudioFormat.Encoding.PCM_SIGNED,
 	                audioFormat.getSampleRate(),
 	                16,
 	                audioFormat.getChannels(),
 	                audioFormat.getChannels() * 2,
 	                audioFormat.getSampleRate(),
 	              false);
 	*/              
 	//            audioInputStream = new org.xiph.speex.spi.Speex2PcmAudioInputStream(fileInputStream, targetFormat, AudioSystem.NOT_SPECIFIED);
 	            audioInputStream = AudioSystem.getAudioInputStream(fileInputStream);
 	// !!!!!!           
 	            if(DEBUG){
 	                System.err.println("--- successfully grabbed audioInputStream by direct rather than SPI ---");
 	            }
 	
 	        } catch (IOException err) {
 	            reportError("IO error while trying to open cache file for playback", err);
 	            return;
 	        } catch(UnsupportedAudioFileException err) {
 	            reportError("'Unsupported audio file' error while trying to open cache file for playback", err);
 	            return;
 	        }
 	       
 	        audioFormat = audioInputStream.getFormat();
 	
 	        DataLine.Info info = new DataLine.Info(SourceDataLine.class, audioFormat);
 	        // If the audioFormat is not directly supported
 	        if (!AudioSystem.isLineSupported(info)) {
            AudioFormat sourceFormat = audioFormat;
 	// !!!!!!! not required
 	/*          AudioFormat targetFormat = new AudioFormat(
 	                AudioFormat.Encoding.PCM_SIGNED,
 	                sourceFormat.getSampleRate(),
 	                16,
 	                sourceFormat.getChannels(),
	                sourceFormat.getChannels() * 2,
	                sourceFormat.getSampleRate(),
 	                false);
 	                
 	      
 	          audioInputStream = AudioSystem.getAudioInputStream(targetFormat, audioInputStream);
 	          audioFormat = audioInputStream.getFormat();
 	
 	          info = new DataLine.Info(SourceDataLine.class, audioFormat);
 	*/
 	//        !!!!!!!
 	        }
 	       
	       
 	        try {
	          sourceDataLine = (SourceDataLine) AudioSystem.getLine(info);
 	          // We have to open the line for it to be ready to receive audio data.
 	          sourceDataLine.open(audioFormat);
	        }
 	        catch (Exception err) {
 	          reportError("Unable to start the audio output based on the required audio parameters.", err);
 	          return;
 	        }
 	
 	
 	        // Once init is complete, then playback can be allowed
 	        audioInitialised = true;
 	        playBut.setEnabled(true);
 	    }
 	   
 	    /**
 	    * This seems to be the best way to "rewind" the stream to the beginning
 	    * after the audio has played (use of "mark/reset" is problematic). You'd
 	    * think there'd be a better way than closing and reopening.
 	    * And maybe there is.
 	    */
 	    private void closeAndReopenStream(){
 	        try{
 	            audioInputStream.close();
 	            // These functions are the same as the initialiseAudio() func
 	            //  except we don't need to re-calculate the stream types
 	            fileInputStream = new BufferedInputStream(new FileInputStream(tmpFile));
 	// !!!!!!           
 	  //          audioInputStream = new org.xiph.speex.spi.Speex2PcmAudioInputStream(fileInputStream, targetFormat, AudioSystem.NOT_SPECIFIED);
	            audioInputStream = AudioSystem.getAudioInputStream(fileInputStream);
 	// !!!!!!               
 	        } catch (IOException err) {
 	            reportError("IO error while trying to RE-open cache file for playback", err);
 	            return;
 	// !!!!!!           
 	        }catch (UnsupportedAudioFileException err) {
 	            reportError("UnsupportedAudioFileException error while trying to RE-open cache file for playback", err);
 	            return;
 	        }
 	// !!!!!!       
 	    }
 	   
 	    /**
 	    * Used when fatal errors occur
 	    */
 	    private void reportError(final String str, final Exception err){
 	        System.err.println("PlayerApplet important error: " + str);
 	        // err.printStackTrace();
        JButton errButt = new JButton("Error");
 	        errButt.addActionListener(new ActionListener(){
 	                       public void actionPerformed(ActionEvent e){
 	                         JOptionPane.showMessageDialog(null, str + "\n\n" + err, "PlayerApplet error", JOptionPane.ERROR_MESSAGE);
 	                       }
 	                       });
 	        try {
 	            BufferedWriter out = new BufferedWriter(new FileWriter("/home/kmaclean/javaerrorfile.txt"));
	            out.write("error" + str + "\n\n" + err + "PlayerApplet error" + JOptionPane.ERROR_MESSAGE);
 	   
	            out.close();
 	        } catch (IOException e) {
 	        }
 	
 	       
	        getContentPane().add(errButt);
	        // !!!!!!
 	        getContentPane().remove(playBut);     
 	        getContentPane().remove(progressBar);
 	        // !!!!!!
 	    }
 	   
	   
	   
 	    /**
 	    * Called by the playback thread to alert the applet when playback stops
 	    */
 	    private void playbackHasStopped(){
 	       playBut.setText("Play");
 	    }
 	   
 	    /**
 	    * Called by the downloader when the amount changes.
 	    * Might be nice to update this not quite as often as every dnld chunk.
 	    */
 	    private void updateDownloadProgress(){
 	       progressBar.setValue((int)(fractionDownloaded() * 100.0));
 	    }
 	
 	   
 	    /**
 	    * If fileSize==-1, will always return zero, since there's no way to know.
 	    * Otherwise returns a float between zero and one.
 	    */
 	    float fractionDownloaded(){
 	        if(fileSize==-1){
 	            return 0.0f;
 	        } else {
 	            return ((float)bytesDownloaded) / (float)fileSize;
 	        }
 	    }
 	   
 	
 	
 	    ////////////////////// Playback thread class //////////////////////
 	
 	    class Playback implements Runnable {
 	        long bytePos = 0L;
 	        protected Thread thread;
 	   
 	        void startPlayback(){
 	            if(DEBUG){
 	                System.err.println("startPlayback() called");
 	            }
 	           
 	            if(isPlaying){
 	                if(DEBUG){
 	                    System.err.println("startPlayback() - isPlaying so not doing anything");
 	                }
 	                return;
 	            }
 	
 	            playBut.setText("Stop");
 	           
 	            isPlaying = true;
 	
 	            thread = new Thread(this);
 	            thread.setName("Playback");
	            thread.start();
 	        }
 	       
 	        void stopPlayback(){
 	            if(DEBUG){
 	                System.err.println("stopPlayback() called");
 	            }
 	            isPlaying = false;
 	        }
 	       
 	        public void run(){
 	            //byte[] buffer = new byte[16384];
 	            byte[] buffer = new byte[4096]; // Smallish to make the "stop" button respond quicker
 	            int bytesRead;
 	            int totalBytesRead=0;           
 	           
 	            if(!audioInitialised) { // This should never happen, really - play button only active after audio is inited
 	                initialiseAudio();
 	            }
 	
 	            closeAndReopenStream(); // This is required for repeated presses of the Play button to work
 	            
 	            sourceDataLine.start();
 	           
 	            try {
 	                while(isPlaying && ((bytesRead = audioInputStream.read(buffer)) != -1 )){
 	                    sourceDataLine.write(buffer, 0, bytesRead);
 	                    totalBytesRead += bytesRead;
 	//System.err.println("Playback thread: read/written "+bytesRead+" bytes. Total: " + totalBytesRead + " bytes. Available: "+audioInputStream.available());
 	
	
 	                    // You'll have 0 bytes available if the buffers are still filling,
 	                    //  or (seemingly) when the file has ended. The JSpeex libraries
 	                    //  don't seem to be dealing with EOF in any sensible way.
 	                    // They should return -1 rather than blocking indefinitely!
 	                    // I hope that "break"ing here doesn't cause problems by
 	                    //  stopping play prematurely - doesn't on my system but
 	                    //  maybe on lower-spec systems...?
 	                    if(audioInputStream.available()==0) {
 	                      break;
 	                    }
 	                }
 	            } catch (IOException err) {
 	                reportError("I/O error in playback thread, while trying to write audio data to output", err);
 	            }
 	
 	            if(DEBUG){
 	                System.err.println("Playback thread: finished - about to drain&stop.");
 	            }
 	           
 	            sourceDataLine.drain(); // Drain the buffer - otherwise the end of the file may never be played
 	            // DON'T STOP - MAY NOT BE ABLE TO PLAY MORE IN FUTURE      sourceDataLine.stop();
	
 	            if(DEBUG){
 	                System.err.println("Playback thread: drained & stopped.");
 	            }
	
 	            playbackHasStopped();
 	            isPlaying = false;
 	        }
 	       
 	    } // End class Playback
 	    
 	   
 	   
 	    ////////////////////// Downloader thread class //////////////////////
 	    
 	    /**
 	         * @author  kmaclean
 	         */
 	    class Downloader extends Thread {
 	   
 	        boolean isComplete = false;
 	   
 	        /**
 	                 * @return
 	                 * @uml.property  name="isComplete"
 	                 */
 	        boolean isComplete(){
 	            return isComplete;
 	        }
 	       
 	        public void run(){
 	            int numBytes = 0;
 	            byte[] byteData = new byte[4096]; // Is this a sensible size? Check later.
 	          
 	            try{
 	                while((numBytes = urlInputStream.read(byteData, 0, byteData.length)) != -1){
 	                    fileOutStream.write(byteData, 0, numBytes);
 	                    bytesDownloaded += numBytes;
 	                    if(fileSize != -1){
 	                        updateDownloadProgress();
 	                    }
 	                    if(numBytes==0){ // Nothing available just yet so...
 	                        yield(); // ...let other threads have a go with the CPU
 	                    }else{
 	//                         if((!headerBytesLoaded) && (bytesDownloaded > 4096)){
 	/*
 	                         if((!headerBytesLoaded) && (tmpFile.length() > 4096)){
 	                             // The amount required above is more than really needed (108 bytes is size of header)
 	                             //   but should make very little difference. A bit of padding for safety.
 	                             //   What's the smallest theoretical Speex file...? Very small!
 	                             headerBytesLoaded = true;
 	                             if(DEBUG){
 	                                 System.err.println("Assuming Ogg Speex header is readable now");
 	                             }
 	                            
 	                             // Trigger the audio format to be interpreted
 	                             initialiseAudio();
 	                            
 	                         }
 	*/
 	                    }
 	                    if(DEBUG){
 	                        System.err.println("Downloader.run(): bytesDownloaded="+bytesDownloaded+", numBytes="+numBytes);
 	                    }
 	                }
 	                fileOutStream.flush();
 	                fileOutStream.close();
 	                if(DEBUG){
 	                    System.err.println("Downloader.run(): COMPLETED!");
 	                }
 	
 	                // This should ideally be run earlier than 100% completion.
 	                // Should only be triggered here if the file is too small to
 	                //  triggered by the check that occurs during the loop above.
 	                if(!headerBytesLoaded) {
 	                    initialiseAudio();
 	                }
 	
 	
 	                isComplete = true;
 	                progressBar.setString("Loaded");
 	                progressBar.setIndeterminate(false);
 	                progressBar.setValue(100);
 	                try{
 	                    Thread.sleep(2000L);
 	                    progressBar.setString("");
 	                }catch(InterruptedException err){
 	                }
 	            }catch(IOException err){
 	                reportError("Downloader.run() encountered IOException while downloading to cache. bytesDownloaded="+bytesDownloaded+", numBytes="+numBytes, err);
 	            }
 	        }
  } // End class Downloader


} // End main class